// FillUp.cpp: implementation of the CFillUp class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Vincent.h"
#include "FillUp.h"

#include "MainFrm.h"

// STL의 stack을 사용하기 위해 include한다.
#include <stack>
using namespace std;


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CFillUp::CFillUp()
{
	m_pbmpOldBuffer = NULL;
	m_pbmpOldShadow = NULL;
}

CFillUp::~CFillUp()
{
	if (m_pbmpOldBuffer)
		m_BufferDC.SelectObject(m_pbmpOldBuffer);
	
	if (m_pbmpOldShadow)
		m_ShadowDC.SelectObject(m_pbmpOldShadow);
}

void CFillUp::Draw(CDC *pDC)
{
	// pDC에 m_BufferDC를 사이즈만큼 복사한다.
	// 그림자를 AND연산으로 그리고, 실제 그림을 PAINT로 그리면, 배경은 제거되고 그려진다.
	// 그림자는 검은색 바탕에 흰색 점이고, 실제 그림은 검은색 바탕에 실제 색상이다.
	pDC->BitBlt(0, 0, m_sizeCanvas.cx, m_sizeCanvas.cy, &m_ShadowDC, 0, 0, SRCAND);		// 그림자를 AND연산으로 그리고,
	pDC->BitBlt(0, 0, m_sizeCanvas.cx, m_sizeCanvas.cy, &m_BufferDC, 0, 0, SRCPAINT);	// 그 위에 실제 그림을 그린다.
}

void CFillUp::OnLButtonDown(CDC *pDC, UINT nFlags, CPoint point, CPoint scrolled)
{
	// 도큐먼트에 직접 접근해서 이미지 사이즈랑 캐쉬DC를 가져온다.
	// 기왕이면 직접 접근하지 않으려고 했는데, 고치려면 많은 구조를 수정해야 한다.
	CVincentDoc* pDoc = ((CVincentDoc*)((CMainFrame*)AfxGetMainWnd())->GetActiveDocument());
	m_sizeCanvas = pDoc->GetImageSize();
	CDC* pCacheDC = pDoc->GetCacheDC();
	
	// 색칠하려는 곳이 원래 색깔이면 관둔다. (결과가 똑같으므로!)
	if (pDC->GetPixel(point) == m_rgbForeground)
		return;
	
	m_ptClicked = point + scrolled;				// 클릭한 위치를 저장해놓고,
	FillUpFirstTime(pDC, pCacheDC, scrolled);	// 그려준다~ (색칠한다.)
	m_bComplete = true;							// 완료한 것으로 설정한다.
}

void CFillUp::FillUpFirstTime(CDC *pDC, CDC *pCacheDC, CPoint scrolled)
{
	// 화면 DC와 호환성 있는 메모리 DC를 만들고, 연결해놓는다.
	m_BufferDC.CreateCompatibleDC(pDC);
	m_bmpBuffer.CreateCompatibleBitmap(pDC, m_sizeCanvas.cx, m_sizeCanvas.cy);
	m_pbmpOldBuffer = (CBitmap*)m_BufferDC.SelectObject(&m_bmpBuffer);
	m_BufferDC.FillSolidRect(0, 0, m_sizeCanvas.cx, m_sizeCanvas.cy, RGB(0,0,0));	// 검은색으로 칠한다.
	
	// 화면 DC와 호환성 있는 그림자용 메모리 DC를 만들고, 연결해놓는다.
	m_ShadowDC.CreateCompatibleDC(pDC);
	m_bmpShadow.CreateCompatibleBitmap(pDC, m_sizeCanvas.cx, m_sizeCanvas.cy);
	m_pbmpOldShadow = (CBitmap*)m_ShadowDC.SelectObject(&m_bmpShadow);
	m_ShadowDC.FillSolidRect(0, 0, m_sizeCanvas.cx, m_sizeCanvas.cy, RGB(0xff,0xff,0xff));	// 흰색으로 칠한다.
	
	// 클릭한 곳의 픽셀의 색상을 얻어온다.
	COLORREF colorClicked = pCacheDC->GetPixel(m_ptClicked);
	
	// 클릭한 점을 중심으로, 주변을 칠한다.. 개념상 재귀적(?)으로..
	stack<PointElement> pointsStack;	// 각 점들을 저장할 스택을 만들고,
	pointsStack.push( PointElement(m_ptClicked) );	// 첫번째 점을 넣어놓고,
	
	while ( !pointsStack.empty() )
	{
		PointElement& current = pointsStack.top();	// 스택의 마지막 점을 직접 사용한다. (레퍼런스로..)
		
		if (current.bInit == false)
		{
			// 왼쪽과 오른쪽의 경계 끝을 검사한다.
			current.left = ScanLeft(pCacheDC, colorClicked, current.point);
			current.right = ScanRight(pCacheDC, colorClicked, current.point);
			current.bInit = true;
		}
		
		// left부터 right까지 선을 그린다. (마지막 점을 포함한다.)
		DrawLineLeft2Right(pDC, scrolled, current.left, current.right, current.point.y);
		
		// 위/아래 줄을 검사..
		if (current.flag == PointElement::NONE)
		{
			// 위 아래 줄을 전부 검사한다.
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.right, PointElement::BELLOW) )
				continue;
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.right, PointElement::UPPER) )
				continue;
		}
		else if (current.flag == PointElement::BELLOW)
		{
			// 아랫줄은 이전줄의 범위보다 큰 부분만 검사하고, 윗줄은 전부 검사.
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.prev_left, PointElement::BELLOW) )
				continue;
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.prev_right, current.right, PointElement::BELLOW) )
				continue;
			
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.right, PointElement::UPPER) )
				continue;
		}
		else if (current.flag == PointElement::UPPER)
		{
			// 윗줄은 이전줄의 범위보다 큰 부분만 검사하고, 아랫줄은 전부 검사.
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.right, PointElement::BELLOW) )
				continue;
			
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.left, current.prev_left, PointElement::UPPER) )
				continue;
			if ( CheckLine(pCacheDC, colorClicked, pointsStack, current.point.y, current.prev_right, current.right, PointElement::UPPER) )
				continue;
		}
		
		pointsStack.pop();
		
	} // while ( !pointsStack.empty() )
}

int CFillUp::ScanLeft(CDC *pDC, COLORREF color, CPoint point)
{
	// color는 클릭한 점의 컬러.
	// 그러면.. 이미 색칠한 점은 어떻게 구별하지?
	while (pDC->GetPixel(point) == color && point.x > 0)
		point.x--;
	
	return ++point.x;
}

int CFillUp::ScanRight(CDC *pDC, COLORREF color, CPoint point)
{
	while (pDC->GetPixel(point) == color && point.x < m_sizeCanvas.cx)
		point.x++;
	
	return --point.x;
}

void CFillUp::DrawLineLeft2Right(CDC *pDC, CPoint scrolled, int left, int right, int y)
{
	// 3개의 DC에.. 선을 그린다. left부터 right까지.
	CPen myPen(PS_SOLID, 1, m_rgbForeground);
	CPen* pOldPen;
	
	// CDC의 LineTo는 마지막 점을 칠하지 않으므로, right를 한 칸 늘린다.
	right++;
	
	// 화면 DC에는 스크롤바의 위치를 고려해서 칠한다.
	pOldPen = pDC->SelectObject(&myPen);
	pDC->MoveTo(left - scrolled.x, y - scrolled.y);
	pDC->LineTo(right - scrolled.x, y - scrolled.y);
	pDC->SelectObject(pOldPen);
	
	// 버퍼 DC에도 칠하고,
	pOldPen = m_BufferDC.SelectObject(&myPen);
	m_BufferDC.MoveTo(left, y);
	m_BufferDC.LineTo(right, y);
	m_BufferDC.SelectObject(pOldPen);
	
	// 그림자 DC는 검은색으로..
	CPen blackPen(PS_SOLID, 1, RGB(0,0,0));
	pOldPen = m_ShadowDC.SelectObject(&blackPen);
	m_ShadowDC.MoveTo(left, y);
	m_ShadowDC.LineTo(right, y);
	m_ShadowDC.SelectObject(pOldPen);
}

bool CFillUp::CheckLine(CDC *pCacheDC, COLORREF colorClicked, stack<PointElement>& pointsStack, int y, int& left, int& right, PointElement::DIRECTION dir)
{
	for (int x = left; x <= right; x++)
	{
		if (pCacheDC->GetPixel(x, y + dir) == colorClicked && m_ShadowDC.GetPixel(x, y + dir) != RGB(0,0,0))
		{
			pointsStack.push( PointElement( CPoint(x, y + dir), left, right, dir * -1 ) );
			left = x;
			return true;
		}
	}
	
	return false;
}
